/* Building on the work done by Chip Cullen for his ligatures.js and by Chengyin Liu
 *    on whatfont.js, this script identifies which of the standard Unicode ligature
 *    characters are available in the displayed font, and activates only those ligatures.
 */

   
/* ------------------------------------------------------------ */
/* NON-USER-MODIFIABLE constants: DO NOT CHANGE                 */
/* ------------------------------------------------------------ */

/* Consts (like enum) for indexing string/html/unicode sets */
var STR = 0;
var LIG = 1;
var UNI = 2;

/* Consts for determining which ligature set to compare to */
var COMMON = 0;
var RARE = 1;
var ALL = 2;

/* Basic parameters for the HTML5 canvas */
var FILL_STYLE = 'rgb(0, 0, 0)';       // canvas fill style
var CLEAR_RECT = 'rgb(255, 255, 255)'; // clear canvas fill style
var HEIGHT = 50;                       // canvas height in px
var WIDTH = 1024;                      // canvas width in px
var SIZE = 40;                         // font size in px
var TEXT_BASELINE = 'top';             // canvas text baseline

var LEN = WIDTH * HEIGHT * 4;          // each pixel is 4 bytes (RGBA)

/* When a ligature is not present in a font, browsers substitute the ligature from the system's sans-serif face*/
var SANS = 'sans-serif';
var SERIF = 'serif';

var ALPHABET = "AbCdEfGhIjKlMnOpQrStUvWxYz";
   
/* Detect canvas support. Only enabled for IE8 and below if using ExplorerCanvas (http://code.google.com/p/explorercanvas/) */
var CANVAS_SUPPORT = $("<canvas>")[0].getContext;

/* ------------------------------------------------------------ */
/* USER-MODIFIABLE constants. You can change these as you like  */
/* ------------------------------------------------------------ */

/* Set the DOM element you want ligaturized here. You can include as many elements as you like; each will be handled separately */
var ELEMENT = 'p, h1, h2, h3, h4';

/* Decide whether to test the font for support of your ligatures. The only reason you would want to turn this off is because you
* don't care what happens with fallback fonts in your stack. It's an option, but not a recommended one. */
var TEST_FOR_FONT_SUPPORT = true;

/* The user can specify which ligatures to use. Note that these encompass all the ligatures
* included in Unicode that can be replaced with regular expressions (I've excluded the
* long-s character because regexps don't find it consistently). This is an incomplete but
* sufficient solution until better OpenType support is available across all modern browsers.
*    COMMON:  ff, fi, fl
*    RARE:    fff, ffi, ffl, ij, IJ, st
*    ALL:     all of the above */
var WHICH_LIGATURES = ALL;

/* ------------------------------------------------------------ */
/* Functions: modify at your own risk                           */
/* ------------------------------------------------------------ */

/* Draw the text on an HTML5 canvas (based directly on Chengyiu Lin's whatfont work) */
function makeTextPixelArray(text, cssFont) {
   
   /* Clear the canvas (paint it white) before proceeding. */
   $context.fillStyle = CLEAR_RECT;
   $context.fillRect(0, 0, WIDTH, HEIGHT);
   
   /* Reset for the contents to be painted on the canvas */
   $context.fillStyle = FILL_STYLE;
   $context.font = cssFont.style + ' ' + cssFont.variant + ' ' + cssFont.weight + ' ' + SIZE + 'px ' + cssFont.family;
   
   /* Paint the canvas with the text at the top left */
   $context.fillText(text, 0, 0);
   
   /* Get the image data as an array for comparison */
   return $context.getImageData(0, 0, WIDTH, HEIGHT).data;
}

/* Find the first non white pixel in a canvas image data array */
function getFirstNonWhitePix(array) {

   var i = 0, found = false;
   while (i < LEN && found === false) {
      if (array[i] !== 255) {
         found = true;
      }
      else {
         i++;
      }
   }
   
   return i;   
}

/* Check if two texts printed on a canvas are identical (based somewhat on Chengyiu Lin's whatfont work) */
function textArraysMatch(pixArray1, pixArray2) {
   
   /* Compare from the first non white pixel, to account for possible line height differences */
   var i = getFirstNonWhitePix(pixArray1);
   var j = getFirstNonWhitePix(pixArray2);
   var identical = true;
   
   /* If any points in the image do not match (strictly equal, no casting), immediately finish the comparison */
   while ( (i < LEN) && (j < LEN) && (identical === true) ) {
   
      if (pixArray1[i] !== pixArray2[j]) {
         identical = false;
      }
      else {
         i++;
         j++;
      }
   }
   
   return identical;
}

/* Get the font styles for a DOM element (based directly on Chengyiu Lin's whatfont work) */
function getCssFont(domElement) {

   var $cssFont = {
      family: $(domElement).css('font-family'),
      style: $(domElement).css('font-style'),
      weight: $(domElement).css('font-weight'),
      variant: $(domElement).css('font-variant')
   };
   
   return $cssFont;
}

/* try each font in cssfontfamily list to see which one is used (based directly on Chengyiu Lin's whatfont work) */
function getFontInUse(cssFont) {
   
   var fonts = cssFont.family.split(',');
   var len = fonts.length;

   var a0 = makeTextPixelArray(ALPHABET, cssFont.family);
   var i;

   for (i = 0; i < len; i += 1) {
      var a1 = makeTextPixelArray(ALPHABET, fonts[i]);

      var serifTestCss = {
         style : cssFont.style,
         variant : cssFont.variant,
         weight : cssFont.weight,
         size : cssFont.size,
         family : fonts[i] + ',serif'
      };
      var sansTestCss = {
         style : cssFont.style,
         variant : cssFont.variant,
         weight : cssFont.weight,
         size : cssFont.size,
         family : fonts[i] + ',sans-serif'
      };
      
      var serifTestArray = makeTextPixelArray(ALPHABET, serifTestCss);
      var sansTestArray = makeTextPixelArray(ALPHABET, sansTestCss);
      
      if (textArraysMatch(a0, a1) && textArraysMatch(serifTestArray, sansTestArray)) {
            
         // rendered fonts match, and font really is installed
         return $.trim(fonts[i]);
      }
   }
}

/* Check whether a ligature is available using HTML5 canvas. */
function isLigAvailable(ligatureUnicode, domElement) {
   
   /* Default to ligature unavailable */
   $ligAvailable = false;
   
   /* Get the css font information from the DOM */
   $domCssFont = getCssFont(domElement);

   /* The element may not exist on the page; if so, return immediately */      
   if ($domCssFont.family == undefined) {
      return $ligAvailable;
   }

   $fontInUse = getFontInUse($domCssFont);
   
   /* Clone the DOM element: style, weight, and variant values are the same. */
   $sansCssFont = jQuery.extend({}, $domCssFont);
   $serifCssFont = $.extend({}, $domCssFont);
   
   /* Change the font family values for each */
   $sansCssFont.family = SANS;
   $serifCssFont.family = SERIF;
   
   /* Convert the unicode numeric representation to an actual text character */
   var ligature = String.fromCharCode(parseInt(ligatureUnicode, 16));
   
   /* Create pixel arrays of the ligature in both the DOM and default fonts */
   var domFontArray = makeTextPixelArray(ligature, $domCssFont);
   var sansFontArray = makeTextPixelArray(ligature, $sansCssFont);
   var serifFontArray = makeTextPixelArray(ligature, $serifCssFont);
   
   /* Compare the canvas text image data arrays */
   var matchesSans = textArraysMatch( domFontArray, sansFontArray );
   var matchesSerif = textArraysMatch( domFontArray, serifFontArray );

   /* If the pixel arrays match a default font, the ligature unavailable unless the entire font is falling back */
   if ( (matchesSans === false) && ( matchesSerif === false ) ) {
      $ligAvailable = true;
   }

   return $ligAvailable;
}

/* Gets a list of and activates available ligatures in the font displayed on the page. */
function getLigatures(which, domElement) {

   /* The most common ligatures available in fonts on the web. */
   var commonLigatures = new Array(
      new Array("ff",  "&#64256;", "FB00"),
      new Array("fi",  "&#64257;", "FB01"),
      new Array("fl",  "&#64258;", "FB02")
      );

   /* Rarer ligatures. */
   var rareLigatures = new Array(
      new Array("ffi", "&#64259;", "FB03"),
      new Array("ffl", "&#64260;", "FB04"),
      new Array("ij", "&#307;", "133"),
      new Array("IJ", "&#306;", "132"),
      new Array("st", "&#64262;", "FB06")
      );
   
   /* Determine which ligatures are available */
   enabledLigatures = new Array();
   
   /* Rare ligatures (must be searched first since they overrule some of the common ligatures, e.g. fff overrules ff */
   if (which == ALL || which == RARE) {
      for (pair in rareLigatures) {
         
         /* Pass the ligature character to be checked */
         if (isLigAvailable(rareLigatures[pair][UNI], domElement)) {
            enabledLigatures.push( new Array( rareLigatures[pair][STR], rareLigatures[pair][LIG] ) );
         }
      }
   }

   /* Common ligatures */
   if (which == ALL || which == COMMON) {
      for (pair in commonLigatures) {
         
         /* Pass the ligature character to be checked */
         if (isLigAvailable(commonLigatures[pair][UNI], domElement)) {
            enabledLigatures.push( new Array( commonLigatures[pair][STR], commonLigatures[pair][LIG] ) );
         }
      }
   }
   
   //alert(enabledLigatures);
   return enabledLigatures;
}

/* Chip Cullen's jQuery ligature function: http://chipcullen.com/ligatures/ */
$.fn.ligature = function (str, lig) {
   return this.each(function () {
      this.innerHTML = this.innerHTML.replace(
         new RegExp(str, "g"),
         lig
      );
   });
}

$.fn.ligatureTest = function() {
   /* Only attempt to check ligature availability if on a browser with canvas support */
   if (CANVAS_SUPPORT) {
      
      /* Initialize the canvas. Note that it is not being attached to the body and so will not be visible */
      $canvas = $('<canvas>')[0];
      
      /* Get the context */
      $('body').append($canvas);
      $context = $canvas.getContext('2d');
      
      /* Set the basic parameters of the canvas */
      $canvas.height = HEIGHT;
      $canvas.width = WIDTH;
      $context.textBaseline = TEXT_BASELINE;

      /* Don't assume that all DOM elements share the same font family. Treat each one individually */
      $elements = ELEMENT.split(',');
      for ($e in $elements) {
         
         $domElement = $.trim($elements[$e]);
         
         $availableLigatures = getLigatures(WHICH_LIGATURES, $domElement);
         
         /* For each available ligature, apply Chip Cullen's jQuery ligature function */
         if ($availableLigatures.length != 0) {
            for ($pair in $availableLigatures) {
               $($domElement).ligature($availableLigatures[$pair][STR], $availableLigatures[$pair][LIG]);
            }
         }         
      }
      
      $($canvas).remove();
      
   }
   return;
}